package fhq.hcmute.qrcode.common;

import java.util.ArrayList;

/**
 * @author Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
public class DataBlock {
	private ArrayList<Block> blocks;

	public DataBlock() {
		blocks = new ArrayList<Block>();
	}

	public DataBlock(Version version, ErrorCorrectionLevel level) {
		blocks = new ArrayList<Block>();
		Version.ECBlocks blocks = version.getCurrentECBlock(level);
		Version.ECB[] ecb = blocks.getECBlocks();
		for (int i = 0; i < ecb.length; i++) {
			for (int j = 0; j < ecb[i].getECBlockCount(); j++) {
				int numberOfDataCodeword = ecb[i].getNumberOfDataCodewords();
				int[] dataCodeWords = new int[numberOfDataCodeword];
				int[] errorCCodeword = new int[blocks.getECCodewordsPerBlock()];
				Block block = new Block(dataCodeWords, errorCCodeword);
				this.blocks.add(block);
			}
		}
	}

	public int getSize() {
		return blocks.size();
	}

	public void add(int[] dataCodeWords, int[] errorCCodewords) {
		Block block = new Block(dataCodeWords, errorCCodewords);
		blocks.add(block);
	}

	public int getMaxDataCodeWord() {
		int max = 0;
		for (Block b : blocks) {
			if (max < b.dataCodeWords.length) {
				max = b.dataCodeWords.length;
			}
		}
		return max;
	}

	public int getMaxErrorCCodeWord() {
		int max = 0;
		for (Block b : blocks) {
			if (max < b.errorCCodewords.length) {
				max = b.errorCCodewords.length;
			}
		}
		return max;
	}

	public Block getBlockAt(int index) {
		return blocks.get(index);
	}

	public class Block {
		private int[] dataCodeWords;
		private int[] errorCCodewords;

		public Block(int[] dataCodeWords, int[] errorCCodewords) {
			this.dataCodeWords = dataCodeWords;
			this.errorCCodewords = errorCCodewords;
		}

		public int[] getDataCodeWords() {
			return dataCodeWords;
		}

		public void setDataCodeWords(int[] dataCodeWords) {
			this.dataCodeWords = dataCodeWords;
		}

		public int[] getErrorCCodewords() {
			return errorCCodewords;
		}

		public void setErrorCCodewords(int[] errorCCodewords) {
			this.errorCCodewords = errorCCodewords;
		}

		public int[] getCodewords() {
			int[] temp = new int[dataCodeWords.length + errorCCodewords.length];
			System.arraycopy(dataCodeWords, 0, temp, 0, dataCodeWords.length);
			System.arraycopy(errorCCodewords, 0, temp, dataCodeWords.length,
					errorCCodewords.length);
			return temp;
		}

		public void setCodewords(int[] Codewords) {
			System.arraycopy(Codewords, 0, dataCodeWords, 0,
					dataCodeWords.length);
			System.arraycopy(Codewords, dataCodeWords.length,
					errorCCodewords, 0, errorCCodewords.length);
		}

		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			for (int i : dataCodeWords) {
				builder.append(i + ", ");
			}
			for (int i : errorCCodewords) {
				builder.append(i + ", ");
			}
			return builder.toString();
		}
	}

	@Override
	public String toString() {
		int totalCodeWords = 0;
		for (Block block : blocks) {
			totalCodeWords += block.dataCodeWords.length;
			totalCodeWords += block.errorCCodewords.length;
		}
		return "Total codewords: " + totalCodeWords;
	}

	public void setDataCodeWordAt(int blockID, int index, int value) {
		if (index < this.getBlockAt(blockID).dataCodeWords.length) {
			this.getBlockAt(blockID).dataCodeWords[index] = value;
		}
	}

	public void setErrorCCodewordAt(int blockID, int index, int value) {
		if (index < this.getBlockAt(blockID).errorCCodewords.length) {
			this.getBlockAt(blockID).errorCCodewords[index] = value;
		}
	}

	public static DataBlock parse(int[] codewords, Version version,
			ErrorCorrectionLevel level) {
		Version.ECBlocks ecbBlock = version.getCurrentECBlock(level);
		Version.ECB[] ecb = ecbBlock.getECBlocks();
		DataBlock dataBlock = new DataBlock(version, level);

		// get Data Code Words.
		for (int i = 0; i < ecb[ecb.length - 1].getNumberOfDataCodewords(); i++) {
			int count = 0;
			int numBlock = ecbBlock.getNumBlocks();
			for (int j = 0; j < ecbBlock.getNumBlocks(); j++) {
				if (i >= dataBlock.getBlockAt(j).dataCodeWords.length) {
					continue;
				}
				dataBlock.setDataCodeWordAt(j, i, codewords[i * numBlock
						+ count]);
				count++;
			}
		}

		// get Error Correction Code Words.
		int previousIndex = version.getTotalDataCodewords(level);
		for (int i = 0; i < ecbBlock.getECCodewordsPerBlock(); i++) {
			int numBlock = ecbBlock.getNumBlocks();
			for (int j = 0; j < ecbBlock.getNumBlocks(); j++) {
				dataBlock.setErrorCCodewordAt(j, i, codewords[i * numBlock + j
						+ previousIndex]);
			}
		}
		return dataBlock;
	}

	private int totalCodewords() {
		int sum = 0;
		for (Block b : this.blocks) {
			sum += b.dataCodeWords.length;
		}
		return sum;
	}

	public int[] getDataCodewords() {
		int[] dataCodewords = new int[totalCodewords()];
		int count = 0;
		for (Block b : this.blocks) {
			for (int i : b.dataCodeWords) {
				dataCodewords[count++] = i;
			}
		}
		return dataCodewords;
	}

	public ArrayList<Block> getBlocks() {
		return blocks;
	}

}
